from sqlalchemy.ext.asyncio import AsyncSession
from starlette.requests import Request

from config.constant import CommonConstant
from exceptions.exception import ServiceException
from module_admin.entity.vo.common_vo import CrudResponseModel
import asyncio
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
import re

import akshare as ak
from module_kylin.dao.future_daily_quote_dao import FutureDailyQuoteDao
from module_kylin.entity.vo.future_daily_quote_vo import FutureDailyQuoteModel, FutureDailyQuotePageQueryModel, \
    DeleteFutureDailyQuoteModel
from utils.common_util import CamelCaseUtil
from utils.log_util import logger

# 创建线程池执行器来运行同步的akshare代码
thread_pool = ThreadPoolExecutor(max_workers=4)


class FutureDailyQuoteService:
    """
    期货日线行情管理模块服务层
    """

    @classmethod
    async def get_quote_list_services(
            cls, query_db: AsyncSession, query_object: FutureDailyQuotePageQueryModel, is_page: bool = False):
        """
        获取期货日线行情列表信息service

        :param query_db: orm对象
        :param query_object: 查询参数对象
        :param is_page: 是否开启分页
        :return: 期货日线行情列表信息对象
        """
        quote_list_result = await FutureDailyQuoteDao.get_quote_list(query_db, query_object, is_page)

        return quote_list_result

    @classmethod
    async def quote_detail_services(cls, query_db: AsyncSession, id: int):
        """
        获取期货日线行情详细信息service

        :param query_db: orm对象
        :param id: 期货日线行情主键id
        :return: 期货日线行情id对应的信息
        """
        quote = await FutureDailyQuoteDao.get_quote_detail_by_id(query_db, id=id)
        if quote:
            result = FutureDailyQuoteModel(**CamelCaseUtil.transform_result(quote))
        else:
            result = FutureDailyQuoteModel(**dict())

        return result

    @classmethod
    async def check_quote_unique_services(cls, query_db: AsyncSession, page_object: FutureDailyQuoteModel):
        """
        校验期货日线行情是否唯一service

        :param query_db: orm对象
        :param page_object: 期货日线行情对象
        :return: 校验结果
        """
        id = -1 if page_object.id is None else page_object.id
        quote = await FutureDailyQuoteDao.get_quote_detail_by_info(
            query_db, FutureDailyQuoteModel(code=page_object.code, date=page_object.date))
        if quote:
            if quote and quote.id != id:
                return CommonConstant.NOT_UNIQUE
        return CommonConstant.UNIQUE

    @classmethod
    async def add_quote_services(cls, query_db: AsyncSession, page_object: FutureDailyQuoteModel):
        """
        新增期货日线行情信息service

        :param query_db: orm对象
        :param page_object: 新增期货日线行情对象
        :return: 新增期货日线行情校验结果
        """
        if not await cls.check_quote_unique_services(query_db, page_object):
            raise ServiceException(
                message=f'新增期货{page_object.code}在{page_object.date}的日线行情失败，该行情已存在')
        else:
            try:
                await FutureDailyQuoteDao.add_quote(query_db, page_object)
                await query_db.commit()
                return CrudResponseModel(is_success=True, message='新增成功')
            except Exception as e:
                await query_db.rollback()
                raise e

    @classmethod
    async def edit_quote_services(cls, query_db: AsyncSession, page_object: FutureDailyQuoteModel):
        """
        编辑期货日线行情信息service

        :param query_db: orm对象
        :param page_object: 编辑期货日线行情对象
        :return: 编辑期货日线行情校验结果
        """
        edit_quote = page_object.model_dump(exclude_unset=True)
        quote_info = await cls.quote_detail_services(query_db, page_object.id)
        if quote_info.id:
            if not await cls.check_quote_unique_services(query_db, page_object):
                raise ServiceException(
                    message=f'修改期货{page_object.code}在{page_object.date}的日线行情失败，该行情已存在')
            else:
                try:
                    await FutureDailyQuoteDao.edit_quote(query_db, edit_quote)
                    await query_db.commit()
                    return CrudResponseModel(is_success=True, message='更新成功')
                except Exception as e:
                    await query_db.rollback()
                    raise e
        else:
            raise ServiceException(message='期货日线行情不存在')

    @classmethod
    def ensure_four_digit_symbol(cls, symbol):
        """
        确保合约代码的数字部分是四位，如果是三位则在前面加2
        
        :param symbol: 合约代码（可能包含交易所信息，如AP603.CZCE）
        :return: 转换后的合约代码
        """
        # 检查是否包含交易所信息
        if '.' in symbol:
            # 分离合约代码和交易所信息
            contract_part, exchange_part = symbol.rsplit('.', 1)
            # 处理合约代码部分
            nums = re.findall(r"\d+", contract_part)
            if nums and len(nums[0]) == 3:
                # 找到数字部分在合约代码中的位置
                digit_index = contract_part.find(nums[0])
                prefix = contract_part[:digit_index]  # 获取数字前的部分
                # 重新组合合约代码和交易所信息
                return f'{prefix}2{nums[0]}.{exchange_part}'
        else:
            # 不包含交易所信息的处理逻辑
            nums = re.findall(r"\d+", symbol)
            if nums and len(nums[0]) == 3:
                # 找到数字部分在符号中的位置
                digit_index = symbol.find(nums[0])
                prefix = symbol[:digit_index]  # 获取数字前的部分
                return prefix + '2' + nums[0]
        return symbol
    
    @classmethod
    async def _save_or_update_quote(cls, query_db: AsyncSession, quote_data: FutureDailyQuoteModel):
        """
        保存或更新期货日线行情数据
        
        :param query_db: 数据库会话对象
        :param quote_data: 期货日线行情数据对象
        :return: 新增计数和更新计数（元组）
        """
        saved = 0
        updated = 0
        
        try:
            # 检查是否已存在该数据
            existing_quote = await FutureDailyQuoteDao.get_quote_detail_by_info(query_db, quote_data)
            
            if existing_quote:
                # 如果存在，更新数据
                quote_data.id = existing_quote.id
                await cls.edit_quote_services(query_db, quote_data)
                updated = 1
            else:
                # 如果不存在，新增数据
                await cls.add_quote_services(query_db, quote_data)
                saved = 1
        except Exception as e:
            logger.error(f'保存或更新期货数据时出错: {str(e)}')
        
        return (saved, updated)
    
    @classmethod
    async def fetch_quote_services(cls, query_db: AsyncSession, symbol: str):
        """
        从外部数据源获取期货日线行情数据并保存到数据库service

        :param query_db: 数据库会话对象
        :param symbol: 期货代码
        :return: 操作结果
        """
        # 保存原始的symbol用于数据库存储
        original_symbol = symbol
        
        # 检查是否是套利合约（含有&符号）
        if '&' in symbol:
            # 套利合约处理逻辑
            return await cls._handle_spread_contract(query_db, original_symbol)
        
        # 普通合约处理逻辑
        # 对symbol进行转换处理用于调用akshare
        # 从合约代码中解析出符号部分（不包含交易所信息）
        if '.' in symbol:
            symbol = symbol[:symbol.index('.')]

        # 应用转换函数
        symbol = cls.ensure_four_digit_symbol(symbol)

        # 先删除该合约代码已有的所有行情数据
        logger.info(f'开始删除期货代码{original_symbol}的已有日线行情数据')
        await FutureDailyQuoteDao.delete_quote_by_code(query_db, original_symbol)
        await query_db.commit()
        logger.info(f'期货代码{original_symbol}的已有日线行情数据删除完成')

        # 使用线程池运行同步的akshare代码
        logger.info(f'开始获取期货代码{symbol}的日线行情数据')

        # 在异步环境中运行同步的akshare代码
        def get_future_data():
            return ak.futures_zh_daily_sina(symbol=symbol)

        futures_df = await asyncio.get_event_loop().run_in_executor(thread_pool, get_future_data)

        if futures_df is None or futures_df.empty:
            logger.warning(f'未获取到期货代码{symbol}的日线行情数据')
            return {'saved_count': 0, 'updated_count': 0, 'deleted_count': 1, 'message': f'未获取到期货代码{symbol}的日线行情数据'}

        logger.info(futures_df)

        # 处理获取到的数据并保存到数据库
        saved_count = 0
        updated_count = 0

        for index, row in futures_df.iterrows():
            try:
                # 转换日期格式 - 从'date'列获取日期值
                date_str = str(row['date'])
                trade_date = datetime.strptime(date_str, '%Y-%m-%d').date()

                # 构建期货行情对象，使用原始的symbol
                quote_data = FutureDailyQuoteModel(
                    code=original_symbol,
                    date=trade_date,
                    open=float(row['open']),
                    high=float(row['high']),
                    low=float(row['low']),
                    close=float(row['close']),
                    volume=int(row['volume']) if row['volume'] else 0,
                    hold=int(row['hold']) if row['hold'] else 0,
                    settle=float(row['settle']) if 'settle' in row and row['settle'] else None
                )

                # 保存或更新数据
                saved, updated = await cls._save_or_update_quote(query_db, quote_data)
                saved_count += saved
                updated_count += updated

            except Exception as e:
                logger.error(f'处理日期{index}的数据时出错: {str(e)}')
                # 继续处理下一条数据
                continue

        logger.info(f'期货代码{symbol}的日线行情数据处理完成，新增{saved_count}条，更新{updated_count}条')
        return {
            'saved_count': saved_count,
            'updated_count': updated_count,
            'deleted_count': 1,  # 表示删除了已有数据
            'message': f'期货代码{original_symbol}的日线行情数据处理完成，删除已有数据，新增{saved_count}条，更新{updated_count}条'
        }
        
    @classmethod
    async def _handle_spread_contract(cls, query_db: AsyncSession, spread_symbol: str):
        """
        处理套利合约的数据
        
        :param query_db: 数据库会话对象
        :param spread_symbol: 套利合约代码
        :return: 操作结果
        """
        try:
            # 解析套利合约代码，格式为 "SPD AP603&AP604.CZCE"
            # 提取交易所信息
            exchange = ''
            if '.' in spread_symbol:
                exchange_part = spread_symbol.split('.')[-1]
                exchange = f'.{exchange_part}'
                
            # 提取包含&符号的合约部分（忽略空格前面的套利指令）
            if '&' in spread_symbol:
                # 如果包含空格，则取空格后的部分
                contract_part = spread_symbol.split(' ')[-1] if ' ' in spread_symbol else spread_symbol
            else:
                raise ServiceException(message=f'无效的套利合约格式: {spread_symbol}')
            
            # 正确处理合约分离，确保主动合约和被动合约都包含正确的交易所信息
            # 例如：SPD AP603&AP604.CZCE -> 主动合约: AP603.CZCE, 被动合约: AP604.CZCE
            if '.' in contract_part and contract_part.endswith(exchange):
                # 移除末尾的交易所信息以便正确分割
                base_part = contract_part[:-len(exchange)]
                active_contract, passive_contract = base_part.split('&')
                active_contract_full = f'{active_contract}{exchange}'
                passive_contract_full = f'{passive_contract}{exchange}'
            else:
                # 如果没有交易所信息或格式不同，直接分割
                active_contract, passive_contract = contract_part.split('&')
                active_contract_full = active_contract
                passive_contract_full = passive_contract
            
            logger.info(f'处理套利合约 {spread_symbol}: 主动合约={active_contract_full}, 被动合约={passive_contract_full}')
            
            # 先删除该套利合约已有的所有行情数据
            logger.info(f'开始删除套利合约{spread_symbol}的已有日线行情数据')
            await FutureDailyQuoteDao.delete_quote_by_code(query_db, spread_symbol)
            await query_db.commit()
            logger.info(f'套利合约{spread_symbol}的已有日线行情数据删除完成')
            
            # 分别获取两个合约的行情数据
            # 使用线程池运行同步的akshare代码获取数据
            def get_active_data():
                return cls._get_contract_data(active_contract_full)
            
            def get_passive_data():
                return cls._get_contract_data(passive_contract_full)
            
            # 并行获取两个合约的数据
            active_data_future = asyncio.get_event_loop().run_in_executor(thread_pool, get_active_data)
            passive_data_future = asyncio.get_event_loop().run_in_executor(thread_pool, get_passive_data)
            
            active_data = await active_data_future
            passive_data = await passive_data_future
            
            if active_data is None or active_data.empty or passive_data is None or passive_data.empty:
                logger.warning(f'未获取到足够的行情数据来计算套利合约 {spread_symbol}')
                return {'saved_count': 0, 'updated_count': 0, 'deleted_count': 1, 
                        'message': f'未获取到足够的行情数据来计算套利合约 {spread_symbol}'}
            
            # 只保留两个合约都有数据的日期
            # 创建日期到数据的映射
            active_dict = {row['date']: row for _, row in active_data.iterrows()}
            passive_dict = {row['date']: row for _, row in passive_data.iterrows()}
            
            # 获取共同日期
            common_dates = set(active_dict.keys()) & set(passive_dict.keys())
            
            if not common_dates:
                logger.warning(f'两个合约没有共同的交易日，无法计算套利合约 {spread_symbol}')
                return {'saved_count': 0, 'updated_count': 0, 'deleted_count': 1, 
                        'message': f'两个合约没有共同的交易日，无法计算套利合约 {spread_symbol}'}
            
            # 处理并保存套利合约数据
            saved_count = 0
            updated_count = 0
            
            for date in sorted(common_dates):
                try:
                    active_row = active_dict[date]
                    passive_row = passive_dict[date]
                    
                    # 转换日期格式
                    trade_date = datetime.strptime(str(date), '%Y-%m-%d').date()
                    
                    # 计算套利合约数据
                    # 严格按照用户需求：价格=主动合约的价格-被动合约的价格
                    open_price = float(active_row['open']) - float(passive_row['open'])
                    high_price = float(active_row['high']) - float(passive_row['high'])
                    low_price = float(active_row['low']) - float(passive_row['low'])
                    close_price = float(active_row['close']) - float(passive_row['close'])
                    
                    # 成交量取两个合约的较小值
                    volume = min(int(active_row['volume']) if active_row['volume'] else 0, 
                                int(passive_row['volume']) if passive_row['volume'] else 0)
                    
                    # 持仓量可以设为0或取两个合约的较小值
                    hold = min(int(active_row['hold']) if active_row['hold'] else 0, 
                              int(passive_row['hold']) if passive_row['hold'] else 0)
                    
                    # 结算价计算
                    settle = None
                    if 'settle' in active_row and active_row['settle'] and 'settle' in passive_row and passive_row['settle']:
                        settle = float(active_row['settle']) - float(passive_row['settle'])
                    
                    # 构建套利合约行情对象
                    spread_quote = FutureDailyQuoteModel(
                        code=spread_symbol,
                        date=trade_date,
                        open=open_price,
                        high=high_price,
                        low=low_price,
                        close=close_price,
                        volume=volume,
                        hold=hold,
                        settle=settle
                    )
                    
                    # 保存或更新数据
                    saved, updated = await cls._save_or_update_quote(query_db, spread_quote)
                    saved_count += saved
                    updated_count += updated
                    
                except Exception as e:
                    logger.error(f'处理套利合约 {spread_symbol} 在日期 {date} 时出错: {str(e)}')
                    continue
            
            logger.info(f'套利合约 {spread_symbol} 的数据处理完成，新增{saved_count}条，更新{updated_count}条')
            return {
                'saved_count': saved_count,
                'updated_count': updated_count,
                'deleted_count': 1,  # 表示删除了已有数据
                'message': f'套利合约 {spread_symbol} 的数据处理完成，删除已有数据，新增{saved_count}条，更新{updated_count}条'
            }
            
        except Exception as e:
            logger.error(f'处理套利合约 {spread_symbol} 时发生错误: {str(e)}')
            raise ServiceException(message=f'处理套利合约失败: {str(e)}')
            
    @classmethod
    def _get_contract_data(cls, symbol):
        """
        同步获取单个合约的日线数据
        
        :param symbol: 合约代码（可能包含交易所信息，如AP603.CZCE）
        :return: pandas DataFrame 或 None
        """
        # 应用转换函数
        symbol = cls.ensure_four_digit_symbol(symbol)
        
        # 调用akshare获取数据时，移除交易所信息
        ak_symbol = symbol.split('.')[0] if '.' in symbol else symbol
        
        try:
            # 同步获取数据，使用不包含交易所信息的合约代码
            return ak.futures_zh_daily_sina(symbol=ak_symbol)
        except Exception as e:
            logger.error(f'获取合约 {symbol} 数据失败: {str(e)}')
            return None

    @classmethod
    async def delete_quote_services(cls, request: Request, query_db: AsyncSession,
                                    page_object: DeleteFutureDailyQuoteModel):
        """
        删除期货日线行情信息service

        :param request: Request对象
        :param query_db: orm对象
        :param page_object: 删除期货日线行情对象
        :return: 删除期货日线行情校验结果
        """
        if page_object.quote_ids:
            quote_id_list = page_object.quote_ids.split(',')
            try:
                for quote_id in quote_id_list:
                    await FutureDailyQuoteDao.delete_quote(query_db, FutureDailyQuoteModel(id=int(quote_id)))
                await query_db.commit()
                return CrudResponseModel(is_success=True, message='删除成功')
            except Exception as e:
                await query_db.rollback()
                raise e
        else:
            raise ServiceException(message='传入期货日线行情id为空')
            
    @classmethod
    async def get_weighted_average_close_price_services(cls, query_db: AsyncSession, code: str, days: int):
        """
        获取特定期货代码和天数的收盘价格加权平均价格service
        
        :param query_db: 数据库会话对象
        :param code: 期货合约代码
        :param days: 计算天数
        :return: 加权平均价格结果
        """
        try:
            # 调用DAO层方法计算加权平均价格
            weighted_avg = await FutureDailyQuoteDao.get_weighted_average_close_price(query_db, code, days)
            
            # 如果没有找到数据或计算结果为空，返回适当的响应
            if weighted_avg is None:
                return {'success': True, 'data': None, 'message': f'未找到期货代码{code}的有效交易数据'}
            
            return {'success': True, 'data': float(weighted_avg), 'message': '计算成功'}
        except Exception as e:
            logger.error(f'计算期货代码{code}的加权平均价格时发生错误: {str(e)}')
            return {'success': False, 'data': None, 'message': f'计算失败: {str(e)}'}
